package fr.cephb.joperon.tool.stypesgui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.swing.AbstractAction;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;


import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.sleepycat.db.DatabaseConfig;
import com.sleepycat.db.DatabaseException;
import com.sleepycat.db.OperationStatus;
import com.sleepycat.db.Transaction;

import fr.cephb.berkeley.baseapi.db.PrimaryDB;
import fr.cephb.berkeley.baseapi.db.Walker;
import fr.cephb.joperon.core.database.PrimaryFidDB;
import fr.cephb.joperon.core.types.Fid;
import fr.cephb.joperon.structures.GTypes;
import fr.cephb.joperon.structures.STypes;
import fr.cephb.joperon.structures.SnpMap;
import fr.cephb.joperon.structures.bindings.SnpMapBinding;
import fr.cephb.joperon.tool.stypesgui.Model;
import fr.cephb.svg.SVG;
import fr.cephb.util.XMLUtilities;
import fr.cephb.xlink.XLink;

/**
 * Scanner
 *
 */
public class Scanner
	{
	private static final Logger LOG=Logger.getLogger("fr.cephb.joperon");
	//private Compilable compiler;
	/** javascript engine */
	private ScriptEngine scriptEngine=null;
	private List<Selector> selectors=new ArrayList<Selector>();
	private Model model;
	private int screen_width=1200;
	private int screen_height=400;
	
	/**
	 * ComponentRacer
	 * motivation: scanning both stypes
	 * and gtype, must find a way to advance both cursors,
	 * sometimes it is just stypes, sometimes it is just gtypes
	 * sometimes both
	 *
	 */

	abstract class ComponentRacer<T>
		{
		Transaction txn=null;
		List<String> path=new ArrayList<String>();
		int database_index=-1;
		PrimaryDB<Fid,T> database=null;
		/** current cursor */
		Walker<Fid, T> walker=null;
		/** key */
		Fid fid=null;
		/** value */
		T value=null;
		
		abstract PrimaryDB<Fid,T> openDb(String path) throws FileNotFoundException,DatabaseException;
		
		
		private void close() throws DatabaseException
			{
			if(this.walker!=null) this.walker.close();
			if(this.database!=null) this.database.close();
			this.walker=null;
			this.database=null;
			}
		
		private boolean _in_range(MatchHandler handler)
			{
			if(handler.chrom_id!=null &&
				( handler.chrom_id!=this.fid.getChromosomeId() ||
				fid.getPosition()>= (handler.view_start+handler.view_length)		)
				) return false;
			return true;
			}
		
		public void advance(final Fid fidStart,MatchHandler handler)
			throws DatabaseException,FileNotFoundException
			{
			if(this.fid!=null)
				{
				return;
				}
			
			if(database==null && database_index+1 < path.size())
				{
				database_index++;
				database=openDb(path.get(database_index));
				walker= database.openWalker(txn);
				if(fidStart!=null &&
					walker.getSearchKeyRange(fidStart)!=OperationStatus.SUCCESS)
					{
					close();
					advance(fidStart,handler);
					return;
					}
				else
					{
					this.fid =walker.getKey();
					this.value= walker.getValue();
					// value larger than fidStart but too high (!= chromosome , etc..)
					if(!_in_range(handler))
						{
						close();
						this.fid=null;
						this.value=null;
						advance(fidStart,handler);
						}
					return;
					}
				}
			else if(database!=null)
				{
				if(walker.getNext()!=OperationStatus.SUCCESS)
					{
					close();
					advance(fidStart, handler);
					return;
					}
				else
					{
					this.fid=walker.getKey();
					this.value= walker.getValue();
					if(!_in_range(handler))
						{
						close();
						this.fid=null;
						this.value=null;
						advance(fidStart,handler);
						}
					return;
					}
				}

			
			}
		}
	

	
	/**
	 * How we should display the results
	 * @author pierre
	 *
	 */
	public static enum Mode
		{
		text,json,xml,svg
		};
	
		
	/**
	 * Box displaying a Genomic object
	 * can have a URL	
	 */
	private static class RangeBox
		{
		int x=0;
		int width=0;
		int count=0;
		String name=null;
		String url=null;
		}
		
	/**
	 * Tuple container for a entry in
	 * the stypes database
	 *
	 */
	public class TableRow
		{
		private Fid fid=null;
		private STypes reference=null;
		private Genotype genotypes[]=null;
		private GTypes gtypes[]=null;
		private SnpMap snpmap=null;
		public Fid getFid()
			{
			return fid;
			}
		public STypes getReference()
			{
			return reference;
			}
		public Genotype[] getGenotypes()
			{
			return genotypes;
			}
		public SnpMap getSnpmap()
			{
			return snpmap;
			}
		
		public GTypes[] getGtypes()
			{
			return gtypes;
			}
		
		@Override
		public String toString()
			{
			return "{TableRow.fid="+getFid()+"}";
			}
		}
		
	/**
	 * MatchHandler
	 *
	 */
	abstract class MatchHandler
		{
		PrintStream out=System.out;
		protected Integer chrom_id=null;
		protected Integer view_start=null;
		protected Integer view_length=null;
		public void startDocument() throws DatabaseException
			{
			
			}
		public void endDocument() throws DatabaseException,IOException
			{
			
			}
		public abstract void found(TableRow row) throws DatabaseException;
		
	
		public void setWriter(PrintStream out)
			{
			this.out = out;
			}
		
		Model.ChromInfo setBounds(Integer chrom_id,Integer view_start,Integer view_end)
			{
			Model.ChromInfo chromInfo=null;
			if(chrom_id!=null)
				{
				chromInfo= Scanner.this.model.getChromById(chrom_id);
				if(chromInfo==null)
					{
					throw new IllegalArgumentException("Unknown chromosome id="+chrom_id);
					}
				
				if(view_end==null || view_end> chromInfo.getLength())
					{
					view_end=chromInfo.getLength();
					}
				
				if(view_start==null)
					{
					view_start= chromInfo.getStart();
					}
				
				if(view_start> chromInfo.getLength())
					{
					view_start=chromInfo.getLength();
					}
				
				int view_length=view_end-view_start;
				
				
				if(view_start+view_length > chromInfo.getLength())
					{
					view_length=chromInfo.getLength()-view_start;
					}
				if(view_start+view_length > Fid.getMaxValue())
					{
					view_length=chromInfo.getLength()-view_start;
					}
				
				this.chrom_id=chromInfo.getId();
				this.view_start=view_start;
				this.view_length=view_length;
				}
			return chromInfo;
			}
		}
	
	/**
	 * TextHandler
	 *
	 */
	private  class TextHandler
		extends MatchHandler
		{
		@Override
		public void startDocument() throws DatabaseException
			{
			out.print("#chr\t"+
					"pos\tfid\t"+
					"base\tsnp\trepeat"
					);
			for(int i=0; i< (Scanner.this.model.getNumberOfSamples()-1)/2;++i)
				{
				String sample="Sample"+(i+1);
				out.print("\t");
				out.print(sample+":A\t");
				out.print(sample+":C\t");
				out.print(sample+":G\t");
				out.print(sample+":T\t");
				out.print(sample+":N\t");
				
				out.print(sample+":Del.\t");
				out.print(sample+":Ins.\t");
				out.print(sample+":Log10.\t");
				out.print(sample+":Alleles");
				}
			out.println();
			}
		@Override
		public void found(TableRow row) throws DatabaseException
			{
			out.print(row.getFid().getChromosomeId()+"\t");
			out.print(row.getFid().getPosition()+"\t");
			out.print(row.getFid().getString()+"\t");
			
			out.print(row.getReference().getMajorBase()+"\t");
			out.print(row.getReference().isSnp()+"\t");
			out.print(row.getReference().isRepeat()+"\t");
			
			
			for(Genotype g:row.getGenotypes())
				{
				out.print("\t"+g.getA());
				out.print("\t"+g.getC());
				out.print("\t"+g.getG());
				out.print("\t"+g.getT());
				out.print("\t"+g.getN());
				out.print("\t"+g.getDeletions());
				out.print("\t"+g.getInsertions());
				out.print("\t"+g.getLog10());
				out.print("\t"+g.getAlleles());
				}
			out.println();
			}
		@Override
		public void endDocument() throws DatabaseException,IOException
			{
			out.flush();
			}
		}
	
	
	private  class JSONHandler
	extends MatchHandler
		{
		long count=0;
		@Override
		public void startDocument() throws DatabaseException
			{
			out.print("[");
			}
		@Override
		public void found(TableRow row) throws DatabaseException
			{
			if(count!=0) out.print(",");
			count++;
			out.print("{");
			
			out.print("'chr':"+row.getFid().getChromosomeId()+",");
			out.print("'pos':"+row.getFid().getPosition()+",");
			out.print("'fid':\'"+row.getFid().getString()+"\',");
			
			out.print("'base':\'"+row.getReference().getMajorBase()+"\',");
			out.print("'snp':"+row.getReference().isSnp()+",");
			out.print("'repeat':"+row.getReference().isRepeat()+",");
			if(row.getSnpmap()!=null)
				{
				out.print("'snpmap:{");
				out.print("'acn':\'"+row.getSnpmap().getAcn()+"\',");
				out.print("'band':\'"+row.getSnpmap().getBand()+"\'");
				out.print('}');
				}
			out.print("'samples':[");
			
			for(int i=0;i< row.getGenotypes().length;++i)
				{
				Genotype g=row.getGenotypes()[i];
				if(i!=0) out.print(",");
				
				out.print("{'A':"+g.getA());
				out.print(",'C':"+g.getC());
				out.print(",'G':"+g.getG());
				out.print(",'T':"+g.getT());
				out.print(",'N':"+g.getN());
				out.print(",'del':"+g.getDeletions());
				out.print(",'ins':"+g.getInsertions());
				out.print(",'log10':"+g.getLog10());
				out.print(",'alleles':\'"+g.getAlleles()+"\'");
				out.print("}");
				}
			out.print("]");
			out.print("}");
			}
		@Override
		public void endDocument() throws DatabaseException,IOException
			{
			out.println("]");
			out.flush();
			}
		}

	/**
	 * 
	 * XMLHandler
	 *
	 */
	private  class XMLHandler
	extends MatchHandler
		{
		
		@Override
		public void startDocument() throws DatabaseException
			{
			out.print("<?xml version='1.0' encoding='UTF-8'?>");
			out.print("<STypesSet>");
			}
		@Override
		public void found(TableRow row) throws DatabaseException
			{
			out.print("<STypes>");
			
			
			out.print("<chrom>"+row.getFid().getChromosomeId()+"</chrom>");
			out.print("<pos>"+row.getFid().getPosition()+"</pos>");
			out.print("<fid>"+row.getFid().getString()+"</fid>");
			
			out.print("<base>"+row.getReference().getMajorBase()+"</base>");
			out.print("<snp>"+row.getReference().isSnp()+"</snp>");
			out.print("<repeat>"+row.getReference().isRepeat()+"</repeat>");
			if(row.getSnpmap()!=null)
				{
				out.print("<SnpMap>");
				out.print("<acn>"+XMLUtilities.escape(row.getSnpmap().getAcn()+"</acn>"));
				out.print("<band>"+XMLUtilities.escape(row.getSnpmap().getBand()+"</band>"));
				out.print("</SnpMap>");
				}
			out.print("<SampleSet>");
			
			for(Genotype g:row.getGenotypes())
				{
				out.print("<Sample>");
				
				if(g.getA()>0) out.print("<a>"+g.getA()+"</a>");
				if(g.getC()>0) out.print("<c>"+g.getC()+"</c>");
				if(g.getG()>0) out.print("<g>"+g.getG()+"</g>");
				if(g.getT()>0) out.print("<t>"+g.getT()+"</t>");
				if(g.getN()>0) out.print("<n>"+g.getN()+"</n>");
				out.print("<del>"+g.getDeletions()+"</del>");
				out.print("<ins>"+g.getInsertions()+"</ins>");
				out.print("<log10>"+g.getLog10()+"</log10>");
				out.print("<alleles>"+g.getAlleles()+"</alleles>");
				out.print("</Sample>");
				}
			out.print("</SampleSet>");
			
			out.print("</STypes>");
			}
		@Override
		public void endDocument() throws DatabaseException
			{
			out.print("</STypesSet>");
			out.flush();
			}
		}
	


	
	/**
	 * SVGHandler
	 * @author pierre
	 *
	 */
	private class SVGHandler
	extends MatchHandler
		{
		private Document dom;
		private Long maximums[];
		private Long minimums[];
		private Long counts[][];
		private TableRow tableRows[]=null;
		private int num_histograms=0;
		private int histogram_width=10;
		SVGHandler()
			{
			try {
				DocumentBuilderFactory f=DocumentBuilderFactory.newInstance();
				f.setCoalescing(true);
				f.setNamespaceAware(true);
				f.setValidating(false);
				f.setExpandEntityReferences(true);
				f.setIgnoringComments(true);
				f.setIgnoringElementContentWhitespace(true);
				DocumentBuilder docBuilder= f.newDocumentBuilder();
				this.dom=docBuilder.newDocument();
			} catch (Exception e)
				{
				throw new RuntimeException(e);
				}

			}
		 @Override
        public void startDocument() throws DatabaseException
            {
			this.num_histograms= screen_width/histogram_width;
			assert(this.num_histograms!=0);
			if(this.view_length< this.num_histograms)
				{
				this.num_histograms=this.view_length;
				this.histogram_width= screen_width/this.num_histograms;
				LOG.info("OK creating a table row for "+this.num_histograms);
				this.tableRows=new TableRow[this.num_histograms];
				}
			else
				{
				LOG.info("this.view_length:="+this.view_length);
				LOG.info("num_histograms:="+this.num_histograms);
				}
			assert(this.num_histograms!=0);
			this.maximums=new Long[num_histograms];
			this.minimums=new Long[num_histograms];
			Arrays.fill(this.maximums, null);
			Arrays.fill(this.minimums, null);
			this.counts=new Long[Scanner.this.selectors.size()][];
			for(int i=0;i< counts.length;++i)
				{
				this.counts[i]=new Long[num_histograms];;
				Arrays.fill(this.counts[i], null);
				}
			assert(counts.length!=0);
			}
		
		 @Override
		 Model.ChromInfo setBounds(Integer chrom_id, Integer start, Integer end)
			 {
			 Model.ChromInfo chromInfo=super.setBounds(chrom_id, start, end);
			 if(chromInfo==null)
					{
					throw new IllegalArgumentException("SVG: undefined chromosome");
					}
			 return chromInfo;
			 }
		 
		 
		private double base2pixel(int x)
			{
			return (double)(((x-view_start)/(double)view_length)*screen_width);
			}
		
		@Override
		public void endDocument() throws DatabaseException,IOException
			{
	
			List<RangeBox> rangeBoxes=new ArrayList<RangeBox>();
			//retrieve the SNP in this region
			Transaction txn=null;
			PrimaryFidDB<SnpMap> snpmapDb=null;
			List<Element> groupForSelectors=new ArrayList<Element>(Scanner.this.selectors.size());
			if(model.getSnpMapPath()!=null)
				{
				Fid fidStart=new Fid(1, this.chrom_id, this.view_start);

				LOG.info("scanning snpmap "+model.getSnpMapPath());
				
				DatabaseConfig dbConfig=new DatabaseConfig();
				dbConfig.setAllowCreate(false);
				dbConfig.setReadOnly(true);
				snpmapDb=new PrimaryFidDB<SnpMap>(
					model.getEnvironment(),
					txn, model.getSnpMapPath(), null, dbConfig,
					new SnpMapBinding()
					);
				boolean first=true;
				Walker<Fid, SnpMap> w= snpmapDb.openWalker(txn);
				
				while((first && fidStart !=null?
					w.getSearchKeyRange(fidStart):
					w.getNext())==OperationStatus.SUCCESS)
					{
					first=false;
					Fid fid= w.getKey();
					if( fid.getChromosomeId()!=this.chrom_id ||
						fid.getPosition()>= (this.view_start+this.view_length)) break;
					SnpMap map=w.getValue();
					if(!map.getAcn().startsWith("rs")) continue;
					
					RangeBox r=new RangeBox();
					r.x=(int)base2pixel(fid.getPosition());
					r.width=Math.max(1,(int)(((double)view_length)/screen_width));
					r.name=map.getAcn();
					r.count=1;
					r.url="http://www.ncbi.nlm.nih.gov/projects/SNP/snp_ref.cgi?rs="+map.getAcn().substring(2);
					if(!rangeBoxes.isEmpty())
						{
						RangeBox last=rangeBoxes.get(rangeBoxes.size()-1);
						if(last.x+last.width>= r.x)
							{
							
							last.width=Math.max(r.width+r.x,last.width+last.x)-last.x;
							if(last.name!=null && !last.name.equals(r.name))
								{
								last.name=null;
								last.url=null;
								}
							else
								{
								last.count++;
								}
							LOG.info("merged");
							r=null;
							}
						}
					if(r!=null)
						{
						LOG.info("new box");
						rangeBoxes.add(r);
						}
					}
				
				w.close();
				snpmapDb.close();
				}
			
			Element svg= this.dom.createElementNS(SVG.NS,"svg");
			this.dom.appendChild(svg);
			svg.setAttribute("xmlns", SVG.NS);
			svg.setAttribute("xmlns:xlink", XLink.NS);
			
			svg.setAttribute("width",String.valueOf(screen_width));
			svg.setAttribute("height",String.valueOf(screen_height));
			
			Element title= this.dom.createElementNS(SVG.NS,"title");
			title.appendChild(dom.createTextNode("chr"+chrom_id+":"+(view_start)+"-"+(view_start+view_length)));
			svg.appendChild(title);
			title= this.dom.createElementNS(SVG.NS,"desc");
			title.appendChild(dom.createTextNode("CNG/CEPH 2009. Created with "+getClass().getName()));
			svg.appendChild(title);
			
			Element script= this.dom.createElementNS(SVG.NS,"title");
			svg.appendChild(script);
			script.setAttribute("type", "text/ecmascript");
			script.appendChild(dom.createCDATASection("\n"));
			
			Element defs= this.dom.createElementNS(SVG.NS,"defs");
			svg.appendChild(defs);
			
			Element grad= this.dom.createElementNS(SVG.NS,"linearGradient");
			defs.appendChild(grad);
			grad.setAttribute("id", "background");
			grad.setAttribute("x1", "50%");
			grad.setAttribute("y1", "0%");
			grad.setAttribute("x2", "50%");
			grad.setAttribute("y2", "100%");
			Element stop= this.dom.createElementNS(SVG.NS,"stop");
			grad.appendChild(stop);
			stop.setAttribute("offset", "0%");
			stop.setAttribute("stop-color","lightgray");
			stop= this.dom.createElementNS(SVG.NS,"stop");
			grad.appendChild(stop);
			stop.setAttribute("offset", "100%");
			stop.setAttribute("stop-color","black");
			
			/** create the gradient defs */
			for(int i=0;i< selectors.size();++i)
				{
				grad= this.dom.createElementNS(SVG.NS,"linearGradient");
				defs.appendChild(grad);
				grad.setAttribute("id", "grad"+i);
				grad.setAttribute("x1", "0%");
				grad.setAttribute("y1", "50%");
				grad.setAttribute("x2", "100%");
				grad.setAttribute("y2", "50%");
				
				String color=Scanner.this.selectors.get(i).color;

				stop= this.dom.createElementNS(SVG.NS,"stop");
				grad.appendChild(stop);
				stop.setAttribute("offset", "0%");
				stop.setAttribute("stop-color",color);
				stop= this.dom.createElementNS(SVG.NS,"stop");
				grad.appendChild(stop);
				stop.setAttribute("offset", "50%");
				stop.setAttribute("stop-color","white");
				stop= this.dom.createElementNS(SVG.NS,"stop");
				grad.appendChild(stop);
				stop.setAttribute("offset", "100%");
				stop.setAttribute("stop-color",color);
				}
			//the drawing itslef
			Element drawing= dom.createElementNS(SVG.NS,"g");
			svg.appendChild(drawing);
			//a background for the drawing
			Element back= dom.createElementNS(SVG.NS,"rect");
			drawing.appendChild(back);
			back.setAttribute("x", "0");
			back.setAttribute("y", "0");
			back.setAttribute("width", String.valueOf(screen_width));
			back.setAttribute("height",String.valueOf(screen_height));
			back.setAttribute("style","fill:url(#background);stroke:black;");
			
			final int num_steps=10;
			float histogram_height= (screen_height*0.75f)/selectors.size();
			int step=(int)(Math.pow(10,Math.ceil(Math.log10(view_length)))/num_steps);
			if(step>=10 && step*2>=view_length)
				{
				step/=10;
				}
			
			if(step>0)
				{
				//create a scale with vertical bars
				Element gscale= dom.createElementNS(SVG.NS, "g");
				drawing.appendChild( gscale);
				gscale.setAttribute("style", "stroke:none;");
				int ticks = view_start - view_start%step;
				//System.err.println("ticks:"+ticks);
				while(ticks<= view_start+view_length)
					{
					float x= (float)base2pixel(ticks);
					Element line= dom.createElementNS(SVG.NS, "rect");
					line.setAttribute("style","fill:white;");
					line.setAttribute("x", String.valueOf(x));
					line.setAttribute("y", "0");
					line.setAttribute("width", "2");
					line.setAttribute("height", String.valueOf(screen_height));
					gscale.appendChild(line);
					line= dom.createElementNS(SVG.NS, "rect");
					line.setAttribute("style","fill:gray;");
					line.setAttribute("x", String.valueOf(x));
					line.setAttribute("y", "0");
					line.setAttribute("width", "1");
					line.setAttribute("height", String.valueOf(screen_height));
					gscale.appendChild(line);
					
					Element t=  dom.createElementNS(SVG.NS, "text");
					t.appendChild(dom.createTextNode(String.valueOf(ticks)));
					t.setAttribute("style", "stroke:gray;");
					t.setAttribute("x", "0");
					t.setAttribute("y", "0");
					t.setAttribute("transform", "translate("+(x+2)+",10) rotate(90)");
					gscale.appendChild(t);
					
					if(step>=10)
						{
						for(int t2=ticks;t2<ticks+step;t2+=step/10)
							{
							float x2= (float)(((t2-view_start)/(double)view_length)*screen_width);
							Element line2= dom.createElementNS(SVG.NS, "line");
							line2.setAttribute("style","stroke:gray;");
							line2.setAttribute("x1", String.valueOf(x2));
							line2.setAttribute("y1", "0");
							line2.setAttribute("x2", String.valueOf(x2));
							line2.setAttribute("y2", String.valueOf(screen_height));
							gscale.appendChild(line2);
							}
						}
					
					ticks+=step;
					}
				
				
				}
			LOG.info("range box size:"+rangeBoxes.size());
			if(!rangeBoxes.isEmpty())
				{
				Element gBoxes= dom.createElementNS(SVG.NS,"g");
				
				
				gBoxes.setAttribute("style","opacity:0.8; stroke:maroon; fill:red;");
				drawing.appendChild(gBoxes);
				for(RangeBox r:rangeBoxes)
					{
					Element rect= dom.createElementNS(SVG.NS, "rect");
					rect.setAttribute("x", String.valueOf(r.x));
					rect.setAttribute("y", "10");
					rect.setAttribute("width", String.valueOf(r.width));
					rect.setAttribute("height", "10");
					
					if(r.name!=null) rect.setAttribute("title",r.name);
					if(r.url!=null)
						{
						Element anchor=dom.createElementNS(SVG.NS, "a");
						anchor.setAttributeNS(XLink.NS,"xlink:href",r.url);
						anchor.appendChild(rect);
						gBoxes.appendChild(anchor);
						}
					else
						{
						gBoxes.appendChild(rect);
						}
					}
				}
			
			
			Element hists= dom.createElementNS(SVG.NS,"g");
			drawing.appendChild(hists);
			hists.setAttribute("style", "stroke:gray;");
			
			/* create subgroup for each histogram */
			for(int j=0;j< selectors.size();++j)
				{
				Element g1= dom.createElementNS(SVG.NS,"g");
				hists.appendChild(g1);
				groupForSelectors.add(g1);
				Selector sel= selectors.get(j);
				g1.setAttribute("style","fill:url(#grad"+j+");stroke:black;");
				g1.setAttribute("title",
						"query :"+  sel.name+
						" log:"+sel.log+
						" min:"+this.minimums[j]+
						" max:"+this.maximums[j]);
				//draw horizontal ticks
				if( maximums[j]!=null &&
					minimums[j]!=null &&
					maximums[j]!=minimums[j])
					{
					double vert=Math.pow(10,Math.ceil(Math.log10(maximums[j]-minimums[j])))/num_steps;
					if(vert>=10 && vert*2>=(maximums[j]-minimums[j]))
						{
						vert/=10;
						}
					long t=minimums[j];
					while(vert > 0 && t<= maximums[j])
						{
						double h=0;
						if(!selectors.get(j).log)
							{
							double hi = t-minimums[j];
							double low= maximums[j]-minimums[j];
						 	h= (hi/low)*histogram_height;
							}
						else if(t==minimums[j])
							{
							h=0;
							}
						else
							{
							double hi = Math.log10(t-minimums[j]);
							double low= Math.log10(maximums[j]-minimums[j]);
						 	h= (hi/low)*histogram_height;
							}
						h= (screen_height-1)-(histogram_height*j + h);
						Element h_axis= dom.createElementNS(SVG.NS,"line");
						//h_axis.setAttribute("title", String.valueOf(h));
						h_axis.setAttribute("x1", "0");
						h_axis.setAttribute("x2", String.valueOf(screen_width));
						h_axis.setAttribute("y1", String.valueOf(h));
						h_axis.setAttribute("y2", String.valueOf(h));
						h_axis.setAttribute("style", "stroke:lightgray;");
						
						g1.appendChild(h_axis);
						t+=vert;
						}
					}
				
				}
			
			
			for(int i=0;i< num_histograms;++i)
				{
				if(this.tableRows!=null && this.tableRows[i]!=null)
					{
					TableRow row= this.tableRows[i];
					float midx= (i*histogram_width)+histogram_width/2f;
					
					Element t=  dom.createElementNS(SVG.NS, "text");
					t.appendChild(dom.createTextNode(String.valueOf(row.getReference().getMajorBase())));
					t.setAttribute("style", "stroke:black;text-anchor:middle;");
					t.setAttribute("x", String.valueOf(midx));
					t.setAttribute("y", "12");
					hists.appendChild(t);
					
					if(row.getSnpmap()!=null && row.getSnpmap().getAcn().matches("rs[0-9]+"))
						{
						Element anchor=dom.createElementNS(SVG.NS, "a");
						anchor.setAttributeNS(XLink.NS,"xlink:href","http://www.ncbi.nlm.nih.gov/projects/SNP/snp_ref.cgi?rs="+row.getSnpmap().getAcn().substring(2));
						t=  dom.createElementNS(SVG.NS, "text");
						t.appendChild(dom.createTextNode(row.getSnpmap().getAcn()));
						t.setAttribute("style", "stroke:blue;text-anchor:middle;");
						t.setAttribute("x","0") ;
						t.setAttribute("y","0");
						t.setAttribute("transform", "translate("+(midx)+",30) rotate(90)");
						anchor.appendChild(t);
						hists.appendChild(anchor);
						}
					}
				
				
				float y=screen_height-1;
				for(int j=0;j< selectors.size();++j)
					{
					//LOG.info("index("+i+") selector("+j+") count:="+counts[j][i]+" max:"+maximums[j]);
					if(maximums[j]!=null &&
					   minimums[j]!=null &&
					   counts[j][i]!=null)
						{
						double h=0;
						if(!selectors.get(j).log)
							{
							double hi = counts[j][i]-minimums[j];
							double low= maximums[j]-minimums[j];
						 	h= (hi/low)*histogram_height;
							}
						//avoid log10==0
						else if(counts[j][i]==minimums[j])
							{
							h=0;
							}
						//avoid log10==0
						else if(maximums[j]==minimums[j])
							{
							h=histogram_height;
							}
						else
							{
							double hi = Math.log10(counts[j][i]-minimums[j]);
							double low= Math.log10(maximums[j]-minimums[j]);
						 	h= (hi/low)*histogram_height;
							}
						
						Element rect= dom.createElementNS(SVG.NS,"rect");
						groupForSelectors.get(j).appendChild(rect);
						rect.setAttribute("title", String.valueOf(counts[j][i]));
						rect.setAttribute("x", String.valueOf(i*histogram_width));
						rect.setAttribute("y", String.valueOf(y-h));
						rect.setAttribute("width", String.valueOf(histogram_width));
						rect.setAttribute("height", String.valueOf(h));
						//rect.setAttribute("style","fill:url(#grad"+j+";stroke:black;");
						}
					y-=histogram_height;
					}
			
				}
			
			try
				{
				TransformerFactory factory=TransformerFactory.newInstance();
				Transformer transformer=factory.newTransformer();
				transformer.transform(new DOMSource(this.dom), new StreamResult(out));
				}
			catch (Exception e)
				{
				throw new RuntimeException(e);
				}
			}
		@Override
		public void found(TableRow row) throws DatabaseException
			{
			int pos=row.getFid().getPosition();
			int index = (int)Math.floor(((pos-this.view_start)/(double)this.view_length)
				*(int)(screen_width/this.histogram_width))
					;
			boolean matched=false;
			for(int i=0;i< selectors.size();++i)
				{
				Integer v=selectors.get(i).eval(row);
				if(v!=null)
					{
					matched=true;
					if(this.counts[i][index]==null)
						{
						this.counts[i][index]=v.longValue();
						}
					else
						{
						this.counts[i][index]+=v;
						}
					
					if(this.maximums[i]==null)
						{
						this.maximums[i]=v.longValue();
						this.minimums[i]=v.longValue();
						}
					else
						{
						this.maximums[i]=Math.max(this.maximums[i],this.counts[i][index]);
						this.minimums[i]=Math.min(this.minimums[i],this.counts[i][index]);
						}
					
					}
				}
			if(matched && this.tableRows!=null)
				{
				this.tableRows[index]=row;
				}
			}
		
		}
	
	/**
	 * Selector
	 * @author pierre
	 *
	 */
	private class Selector
		{
		private Exception error=null;
		//@SuppressWarnings("unused")
		//private String fileName;
		//private CompiledScript script;
		//private Bindings bindings;
		
		private boolean log=false;
		private String color="black";
		private String fun_eval=null;
		private String name="undefined";
		
		public Integer eval(final TableRow row)
			{

			try
				{
				//this.bindings.put("entry",row);
				Object o= Invocable.class.cast(Scanner.this.scriptEngine).invokeFunction(this.fun_eval, row);

				if(o==null) return null;
				else if(o instanceof Integer)
					{
					return Integer.class.cast(o);
					}
				else if(o instanceof Boolean)
					{
					return Boolean.class.cast(o).booleanValue()?1:null;
					}
				else if(o instanceof Short)
					{
					return Short.class.cast(o).intValue();
					}
				else if(o instanceof Byte)
					{
					return Byte.class.cast(o).intValue();
					}
				else if(o instanceof Float || o instanceof Double)
					{
					double d= Number.class.cast(o).doubleValue();
					if(d>=Integer.MIN_VALUE && d<=Integer.MAX_VALUE)
						{
						return (int)d;
						}
					}
				throw new IllegalArgumentException("Bad number returned by script "+o +" "+o.getClass());
				}
			catch(Exception err)
				{
				if(error==null)
					{
					LOG.info(err.getMessage());
					error=err;
					System.err.println(this.name+" eval="+this.fun_eval );
					err.printStackTrace();
					}
				return null;
				}
			}
		}
	
	/* private */ Scanner()
		{
		ScriptEngineManager manager = new ScriptEngineManager();
		this.scriptEngine = manager.getEngineByName("js");
		if(this.scriptEngine==null) throw new NullPointerException("no js engine");
		
		}
	
	
	
	public void setModel(Model model)
		{
		if(this.model!=null) throw new IllegalStateException("model already set");
		this.model=model;
		}
	
	
	
	public void addQueries(InputSource source)
		throws IOException,SAXException,ParserConfigurationException,ScriptException
		{
		final String schemaName="config.xsd";
		LOG.info("reading schema "+schemaName);
		SchemaFactory factory=SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
		InputStream schemaIn= Model.class.getResourceAsStream(schemaName);
		if(schemaIn==null) throw new IOException("Cannot get schema "+schemaName);
		Schema schemaForQuery=factory.newSchema(new StreamSource(schemaIn));
		schemaIn.close();
			
		
		DocumentBuilderFactory f=DocumentBuilderFactory.newInstance();
		f.setCoalescing(true);
		f.setNamespaceAware(false);
		f.setValidating(true);
		f.setSchema(schemaForQuery);
		f.setExpandEntityReferences(true);
		f.setIgnoringComments(false);
		f.setIgnoringElementContentWhitespace(true);
		DocumentBuilder docBuilder= f.newDocumentBuilder();
		Document queryXml=docBuilder.parse(source);
		
		Element root=queryXml.getDocumentElement();
		assert(root!=null);
		for(Node n1=root.getFirstChild();
			n1!=null;
			n1=n1.getNextSibling())
			{
			if(n1.getNodeName().equals("script"))
				{
				this.scriptEngine.eval(n1.getTextContent());
				}
			else if(n1.getNodeName().equals("selectors"))
				{
				for(Node n2=n1.getFirstChild();
					n2!=null;
					n2=n2.getNextSibling())
					{
					if(n2.getNodeName().equals("selector"))
						{
						Selector selector=new Selector();
						for(Node n3=n2.getFirstChild();
							n3!=null;
							n3=n3.getNextSibling())
							{
							if(n3.getNodeName().equals("log"))
								{
								selector.log = Boolean.parseBoolean(n3.getTextContent().trim());
								}
							else if(n3.getNodeName().equals("name"))
								{
								selector.name = n3.getTextContent().trim();
								}
							else if(n3.getNodeName().equals("color"))
								{
								selector.color = n3.getTextContent().trim();
								}
							else if(n3.getNodeName().equals("call"))
								{
								selector.fun_eval = n3.getTextContent().trim();
								}
							}
						this.selectors.add(selector);
						}
					}
				}
			}
		if(this.selectors.isEmpty())
			{
			throw new IllegalArgumentException(
				"No query was found"	
				);
			}
		}
	
	 void setScreenSize(int screen_width,int screen_height)
		{
		if(screen_height>20)
			this.screen_height = screen_height;
		if(screen_height>20)
			this.screen_width = screen_width;
		}
	
	 
	 void scan2(MatchHandler handler) throws DatabaseException,FileNotFoundException
	 	{
		Fid fidStart=null;
		if(handler.chrom_id!=null)
			{
			fidStart=new Fid(1, handler.chrom_id, handler.view_start);
			LOG.info("fid start:"+ fidStart);
			}
		Transaction txn=null;
		PrimaryFidDB<SnpMap> snpmapDb=null;
		if(model.getSnpMapPath()!=null)
			{
			LOG.info("opening snmap: "+model.getSnpMapPath());
			DatabaseConfig dbConfig=new DatabaseConfig();
			dbConfig.setAllowCreate(false);
			dbConfig.setReadOnly(true);
			snpmapDb=new PrimaryFidDB<SnpMap>(
				model.getEnvironment(),
				txn, model.getSnpMapPath(), null, dbConfig,
				new SnpMapBinding()
				);
			}
		
		
		ComponentRacer<STypes[]> stypeRacer= new ComponentRacer<STypes[]>()
			{
			@Override
			PrimaryDB<Fid, STypes[]> openDb(String path)
					throws FileNotFoundException, DatabaseException
				{
				return model.openSTypesDB(path);
				}
			};
		stypeRacer.path.addAll( new ArrayList<String>(model.getSTypes()));
		
		
		ComponentRacer<GTypes[]> gtypeRacer= new ComponentRacer<GTypes[]>()
			{
			@Override
			PrimaryDB<Fid, GTypes[]> openDb(String path)
					throws FileNotFoundException, DatabaseException
				{
				return model.openGTypesDB(path);
				}
			};
		String gtypepath= model.getGTypes();
		if(gtypepath!=null)
			{
			gtypeRacer.path.add(gtypepath);
			}
		
		ComponentRacer<?> racers[]=new ComponentRacer[]{stypeRacer,gtypeRacer};
		
		for(ComponentRacer<?> c:racers)
			{
			c.txn=txn;
			}
		
		while(true)
			{
			Fid lowest=null;
			for(ComponentRacer<?> c:racers)
				{
				if(c.fid!=null) continue;
				c.advance(fidStart,handler);
				if(c.fid==null) continue;
				if(lowest==null || c.fid.compareTo(lowest)<0)
					{
					lowest=c.fid;
					}
				}
			if(lowest==null) break;
			
			TableRow row= new TableRow();
			row.fid=lowest;
			if(lowest.equals(stypeRacer.fid))
				{
				STypes stypes[]=stypeRacer.value;
				row.reference=stypes[0];
				row.genotypes=new Genotype[(stypes.length-1)/2];
				for(int i=1;i+1<stypes.length;i+=2)
					{
					row.genotypes[(i-1)/2]=new Genotype(stypes[i],stypes[i+1]);
					}
				}
			
			if(lowest.equals(gtypeRacer.fid))
				{
				row.gtypes= gtypeRacer.value;
				}
			
			if(snpmapDb!=null)
				{
				row.snpmap=snpmapDb.get(txn, row.fid);
				}
			
			boolean ok=false;
			for(Selector sel: this.selectors)
				{
				if(sel.eval(row)!=null)
					{
					ok=true;
					break;
					}
				}
			if(!ok) continue;
			handler.found(row);
			
			
			for(ComponentRacer<?> c:racers)
				{
				if(c.fid==null) continue;
				if(c.fid.compareTo(lowest)<=0)
					{
					c.fid=null;
					c.value=null;
					}
				}
			
			}
		for(ComponentRacer<?> c:racers)
			{
			c.close();
			}

		if(snpmapDb!=null)
			{
			snpmapDb.close();
			}
		}
	 
	
	/*private*/ void scan(MatchHandler handler) throws DatabaseException,FileNotFoundException
		{
	    long count=0L;
		boolean first=true;
		Fid fidStart=null;
		if(handler.chrom_id!=null)
			{
			fidStart=new Fid(1, handler.chrom_id, handler.view_start);
			LOG.info("fid start:"+ fidStart);
			}
		Transaction txn=null;
		PrimaryFidDB<SnpMap> snpmapDb=null;
		if(model.getSnpMapPath()!=null)
			{
			LOG.info("opening snmap: "+model.getSnpMapPath());
			DatabaseConfig dbConfig=new DatabaseConfig();
			dbConfig.setAllowCreate(false);
			dbConfig.setReadOnly(true);
			snpmapDb=new PrimaryFidDB<SnpMap>(
				model.getEnvironment(),
				txn, model.getSnpMapPath(), null, dbConfig,
				new SnpMapBinding()
				);
			}
		
		for(String path:model.getSTypes())
			{
			LOG.info("scanning "+path);
			first=true;
			PrimaryDB<Fid, STypes[]> stypesdb=model.openSTypesDB(path);
			Walker<Fid, STypes[]> w= stypesdb.openWalker(txn);
			while(
				(first && fidStart !=null?
				w.getSearchKeyRange(fidStart):
				w.getNext())==OperationStatus.SUCCESS)
				{
				first=false;
				TableRow row= new TableRow();
				row.fid=w.getKey();
			
		
				if(handler.chrom_id!=null &&
					( handler.chrom_id!=row.fid.getChromosomeId() ||
					 row.fid.getPosition()>= (handler.view_start+handler.view_length)		)
					)
					{
					LOG.info("handler.chrom_id:"+handler.chrom_id);
					LOG.info("fid.chrom:"+ row.fid.getChromosomeId());
					LOG.info("row.pos:"+row.fid.getPosition());
					LOG.info("end :"+(handler.view_start+handler.view_length));
					LOG.info("breat at "+row.fid.getString());
					break;
					}
				count++;
				//if(count>1000) break;
				STypes stypes[]=w.getValue();
				row.reference=stypes[0];
				row.genotypes=new Genotype[(stypes.length-1)/2];
				for(int i=1;i+1<stypes.length;i+=2)
					{
					row.genotypes[(i-1)/2]=new Genotype(stypes[i],stypes[i+1]);
					}
				if(snpmapDb!=null)
					{
					row.snpmap=snpmapDb.get(txn, row.fid);
					}
				boolean ok=false;
				for(Selector sel: this.selectors)
					{
					if(sel.eval(row)!=null)
						{
						ok=true;
						break;
						}
					}
				if(!ok) continue;
				handler.found(row);
				}
			LOG.info("End of scan");
			w.close();
			stypesdb.close();
			}
		if(snpmapDb!=null)
			{
			snpmapDb.close();
			}
		}

	MatchHandler newHandler(Mode m)
		{
		switch(m)
			{
			case text: return new TextHandler();
			case xml: return new  XMLHandler();
			case json: return new JSONHandler();
			case svg: return new SVGHandler();
			default:throw new UnsupportedOperationException("Not implemented "+m);
			}
		}
	
	/** GUI launcher */
	public static class Launcher
		extends JPanel
		{
		private static final long serialVersionUID = 1L;
		private JTextArea scriptAreas[];
		private JTextField txtFieldCfgFile=null;
		private JTextField txtFieldChrom=null;
		private JTextField txtFieldStart=null;
		private JTextField txtFieldEnd=null;
		private JComboBox comboFormat=null;
		private JTextField txtOuputFile=null;
		private JProgressBar progress=null;
		private Launcher()
			{
			super(new BorderLayout());
			setBorder(new EmptyBorder(5, 5, 5, 5));
			JPanel pane= new JPanel(new GridLayout(0, 1,5,5));
			add(pane,BorderLayout.CENTER);
			JPanel pane2= new JPanel();
			BoxLayout layout= new BoxLayout(pane2, BoxLayout.Y_AXIS);
			pane2.setLayout(layout);
			pane.add(pane2);
			
			JPanel pane5= new JPanel(new FlowLayout(FlowLayout.LEADING));
			pane5.add(new JLabel("Config file:",JLabel.RIGHT));
			pane5.add(txtFieldCfgFile=new JTextField(20));
			pane2.add(pane5);
			
			pane5= new JPanel(new FlowLayout(FlowLayout.LEADING));
			pane5.add(new JLabel("Chromosome:",JLabel.RIGHT));
			pane5.add(txtFieldChrom=new JTextField(5));
			pane2.add(pane5);
			
			pane5= new JPanel(new FlowLayout(FlowLayout.LEADING));
			pane5.add(new JLabel("Start:",JLabel.RIGHT));
			pane5.add(txtFieldStart=new JTextField(5));
			pane2.add(pane5);
			
			pane5= new JPanel(new FlowLayout(FlowLayout.LEADING));
			pane5.add(new JLabel("End:",JLabel.RIGHT));
			pane5.add(txtFieldEnd=new JTextField(5));
			pane2.add(pane5);
			
			pane5= new JPanel(new FlowLayout(FlowLayout.LEADING));
			pane5.add(new JLabel("Format:",JLabel.RIGHT));
			pane5.add(comboFormat=new JComboBox(Scanner.Mode.values()));
			pane2.add(pane5);
			
			pane5= new JPanel(new FlowLayout(FlowLayout.LEADING));
			pane5.add(new JLabel("Output File:",JLabel.RIGHT));
			pane5.add(txtOuputFile=new JTextField(20));
			txtOuputFile.setEditable(false);
			pane5.add(new JButton(new AbstractAction("Choose...")
				{
				private static final long serialVersionUID = 1L;

				@Override
				public void actionPerformed(ActionEvent arg0)
					{
					JFileChooser fc=new JFileChooser();
					if(fc.showSaveDialog(Launcher.this)!=JFileChooser.APPROVE_OPTION) return;
					File f= fc.getSelectedFile();
					if(f==null || (f.exists() && JOptionPane.showConfirmDialog(Launcher.this, "File "+f+" exists. Overwrite ?","Question",JOptionPane.OK_CANCEL_OPTION,JOptionPane.WARNING_MESSAGE,null)!=JOptionPane.OK_OPTION))
						{
						return;
						}
					txtOuputFile.setText(f.toString());
					txtOuputFile.setCaretPosition(0);
					}
				}));
			pane2.add(pane5);
			
			JPanel pane3= new JPanel(new GridLayout(1,0,5,5));
			pane.add(pane3);
			this.scriptAreas=new JTextArea[3];
			for(int i=0;i< this.scriptAreas.length;++i)
				{
				JPanel pane4= new JPanel(new BorderLayout(5,5));
				pane3.add(pane4);
				pane4.setBorder(new TitledBorder("Script "+(i+1)));
				this.scriptAreas[i]=new JTextArea(20,25);
				this.scriptAreas[i].setFont(new Font("courier",Font.PLAIN,10));
				pane4.add(new JScrollPane(this.scriptAreas[i]));
				}
			JPanel bottom= new JPanel(new FlowLayout(FlowLayout.TRAILING));
			add(bottom,BorderLayout.SOUTH);
			bottom.add(progress=new JProgressBar());
			progress.setPreferredSize(new Dimension(200,20));
			bottom.add(new JButton(new AbstractAction("Run...")
				{
				private static final long serialVersionUID = 1L;

				@Override
				public void actionPerformed(ActionEvent e)
					{
					List<String> args=new ArrayList<String>();
					if(txtFieldCfgFile.getText().length()==0)
						{
						return;
						}
					args.add("-cfg");args.add(txtFieldCfgFile.getText().trim());
					if(txtFieldChrom.getText().length()>0)
						{
						args.add("-c");args.add(txtFieldChrom.getText().trim());
						}
					if(txtFieldStart.getText().length()>0)
						{
						args.add("-s");args.add(txtFieldStart.getText().trim());
						}
					if(txtFieldEnd.getText().length()>0)
						{
						args.add("-e");args.add(txtFieldEnd.getText().trim());
						}
					for(JTextArea area: scriptAreas)
						{
						if(area.getText().trim().length()>0)
							{
							args.add("-J");args.add(area.getText().trim());
							}
						}
					args.add("-m");args.add(Mode.class.cast(comboFormat.getSelectedItem()).name());
					if(txtOuputFile.getText().length()>0)
						{
						args.add("-o");args.add(txtOuputFile.getText().trim());
						}
					
					final String argv[]=args.toArray(new String[args.size()]);
					
					
					Thread thread= new Thread()
						{
						@Override
						public void run()
							{
							try
								{
								Scanner.main(argv);
								}
							catch (final Exception e2)
								{
								e2.printStackTrace();
								try {SwingUtilities.invokeAndWait(new Runnable()
									{
									@Override
									public void run()
										{
										JOptionPane.showMessageDialog(Launcher.this, String.valueOf(e2.getMessage()),"Error",JOptionPane.ERROR_MESSAGE);
										}
									}); } catch(Exception e3)
									{
									e3.printStackTrace();
									}
								}
							finally
								{
								try {SwingUtilities.invokeAndWait(new Runnable()
									{
									@Override
									public void run()
										{
										progress.setIndeterminate(false);
										setEnabled(true);
										}
									}); } catch(Exception e3)
									{
									e3.printStackTrace();
									}
								}
							}
						};
					progress.setIndeterminate(true);
					this.setEnabled(false);
					thread.start();
					}
				}));
			}
		public static void main(String[] args)
			{
			try
				{
				Launcher launcher=new Launcher();
				final JFrame frame= new JFrame("STypes");
				frame.setContentPane(launcher);
				frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
				frame.pack();
				SwingUtilities.invokeAndWait(new Runnable()
					{
					@Override
					public void run()
						{
						frame.setVisible(true);
						Dimension d=Toolkit.getDefaultToolkit().getScreenSize();
						frame.setLocation(
							(d.width-frame.getWidth())/2,
							(d.height-frame.getHeight())/2
							);
						}
					});
				}
			catch (Exception e)
				{
				LOG.warning(e.getMessage());
				JOptionPane.showMessageDialog(null,String.valueOf(e.getMessage()),"Error",JOptionPane.ERROR_MESSAGE);
				}
			}
		}
	
	/**
	 * Main
	 * @param args
	 */
	public static void main(String[] args) throws Exception
		{
		LOG.setLevel(Level.OFF);
		Scanner scanner= new Scanner();
		File fileout=null;
		File cfgHome=null;
		
		Integer chrom_id=null;
		Integer view_start=null;
		Integer view_end=null;
		Mode mode=Mode.text;
		File selectorFile=null;
		
		try
			{
			int optind=0;
			while(optind< args.length)
				{
				if(args[optind].equals("-h"))
					{
					System.err.println(" -log-level <java.util.logging.Level>");
					System.err.println(" -cfg <config file>");
					System.err.println(" -c chromosome-id (required for SVG)");
					System.err.println(" -s chromosome-start ");
					System.err.println(" -e chromosome-length");
					
					System.err.println(" -j <javascript file> . Add the given javascript query filter");
					System.err.println(" -o fileout <file> (optional, default stdout)");
					System.err.print(" -m <mode> one of ");for(Mode m:Mode.values()) System.err.print(m.toString()+" ");System.err.println();
					System.err.println("With mode= svg:");
					System.err.println(" -svg-width <integer> default:"+scanner.screen_width);
					System.err.println(" -svg-height <integer> default:"+scanner.screen_height);
					
					System.err.println(" ");
					return;
					}
				else if(args[optind].equals("-svg-width"))
					{
					int i=Integer.parseInt(args[++optind]);
					if(i>10) scanner.screen_width=i; 
					}
				else if(args[optind].equals("-svg-height"))
					{
					int i=Integer.parseInt(args[++optind]);
					if(i>10) scanner.screen_height=i; 
					}
				else if(args[optind].equals("-c"))
					{
					chrom_id=Integer.parseInt(args[++optind]);
					}
				else if(args[optind].equals("-s"))
					{
					view_start=Integer.parseInt(args[++optind]);
					}
				else if(args[optind].equals("-e"))
					{
					view_end=Integer.parseInt(args[++optind]);
					}
				else if(args[optind].equals("-log-level"))
					{
					LOG.setLevel(Level.parse(args[++optind]));
					}
				else if(args[optind].equals("-cfg"))
					{
					cfgHome= new File(args[++optind]);
					}
				else if(args[optind].equals("-j"))
					{
					selectorFile=new File(args[++optind]);
					}
				else if(args[optind].equals("-o"))
					{
					fileout= new File(args[++optind]);
					}
				else if(args[optind].equals("-m"))
					{
					mode= Mode.valueOf(args[++optind]);
					}
				else if(args[optind].equals("--"))
					{
					optind++;
					break;
					}
				else if(args[optind].startsWith("-"))
					{
					System.err.println("Unknown option "+args[optind]);
					}
				else 
					{
					break;
					}
				++optind;
				}
			
			if(cfgHome==null)
				{
				System.err.println("Undefined config home home");
				return;
				}
			
			if(chrom_id==null && (view_start!=null || view_end!=null))
				{
				System.err.println("Chromosome missing");
				return;
				}
			
			if(view_start!=null && view_end!=null && view_start> view_end)
				{
				System.err.println("start>end");
				return;
				}
			
			if(selectorFile==null)
				{
				System.err.println("no query file");
				return;
				}
			
			LOG.info("reading script "+selectorFile);
			FileInputStream fin= new FileInputStream(selectorFile);
			scanner.addQueries(new InputSource(fin));
			fin.close();
			
		
			scanner.setModel( new Model());
			LOG.info("opening "+cfgHome);
			scanner.model.open(cfgHome);
			
			
			LOG.info("creating handler");
			MatchHandler handler=scanner.newHandler(mode);
			handler.setBounds(chrom_id,view_start,view_end);
			
			
			
				
			
			LOG.info("chrom:"+handler.chrom_id);
			LOG.info("start:"+handler.view_start);
			LOG.info("length:"+handler.view_length);
			

			handler.out=System.out;
			if(fileout!=null)
				{
				handler.out= new PrintStream(fileout);
				}
			handler.startDocument();
			scanner.scan(handler);
			handler.endDocument();
			
			handler.out.flush();
			if(fileout!=null)
				{	
				handler.out.close();
				}
			} 
		catch(Exception err)
			{
			err.printStackTrace();
			throw err;
			}
		finally
			{
			if(scanner.model!=null) try
				{
				scanner.model.close();
				}
			catch (DatabaseException e)
				{
				e.printStackTrace();
				}
			}
		}
	}
